#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
任务服务
处理录制、转码、转推等任务的创建和管理
- 录制
D:\bin\ffmpeg\bin\ffmpeg.exe -i "rtmp://10.31.32.47:1935/live/1234"  -c copy -f hls -hls_time 600 -hls_list_size 0 -hls_flags append_list -hls_playlist_type event -hls_segment_filename "D:\share\test\record\9\segment_%03d.ts" "D:\share\test\record\9\index.m3u8"
- 转推

- 转码
"""

import os
import uuid
from pathlib import Path
from typing import Dict, Any
from datetime import datetime
import logging

# 导入转码模板模型
from app.models.transcode_template import TranscodeTemplate

# 初始化日志
logger = logging.getLogger(__name__)

class TaskService:
    """任务服务"""
    
    def __init__(self):
        # 从配置获取所有参数
        from app.core.config import settings
        self.recording_path = settings.RECORDING_PATH
        self.ffmpeg_path = settings.FFMPEG_PATH
        
        # 使用配置文件中的HLS参数
        self.hls_segment_duration = settings.HLS_SEGMENT_DURATION or 60  # 默认60秒
        self.hls_playlist_type = settings.HLS_PLAYLIST_TYPE or "event"
        
        # 确保路径是绝对路径
        self.recording_path = os.path.abspath(self.recording_path)
        
        # 确保目录存在
        Path(self.recording_path).mkdir(parents=True, exist_ok=True)
    
    def _get_ffmpeg_command(self) -> str:
        """获取FFmpeg可执行文件路径"""
        if os.path.isfile(self.ffmpeg_path):
            return f'"{self.ffmpeg_path}"'
        else:
            # 回退到系统路径
            return "ffmpeg"
    
    def _sanitize_filename(self, filename: str) -> str:
        """清理文件名"""
        import re
        # 移除危险字符
        filename = re.sub(r'[<>:"|?*\x00-\x1f]', '', filename)
        filename = filename.replace('..', '').replace('/', '').replace('\\', '')
        return filename.strip()
    
    def build_recording_command(self, stream_url: str, output_dir: str, task_id: str) -> str:
        """构建录制命令"""
        ffmpeg_cmd = self._get_ffmpeg_command()
        
        # 清理任务ID作为目录名
        safe_task_id = self._sanitize_filename(task_id)
        output_path = os.path.join(output_dir, safe_task_id, "index.m3u8")
        
        # 确保输出目录存在
        Path(os.path.dirname(output_path)).mkdir(parents=True, exist_ok=True)
        
        # 构建HLS录制命令
        segment_pattern = os.path.join(os.path.dirname(output_path), 'segment_%08d.ts')
        command = f'{ffmpeg_cmd} -i "{stream_url}" -timeout 30000 -c copy -f hls -hls_time {self.hls_segment_duration} -hls_list_size 0 -hls_flags append_list -hls_playlist_type {self.hls_playlist_type} -hls_segment_filename "{segment_pattern}" "{output_path}"'
        
        return command.strip()
    

    
    def build_relay_command(self, input_url: str, output_url: str) -> str:
        """构建转推命令（直接复制流，不进行转码）"""
        ffmpeg_cmd = self._get_ffmpeg_command()
        
        # 构建转推命令 - 始终使用流复制模式
        command = f'{ffmpeg_cmd} -i "{input_url}" -timeout 30000 -c copy -f flv "{output_url}"'
        
        return command.strip()
    
    def build_transcode_command_live(self, input_url: str, output_url: str, 
                              tempid: int = None, db=None) -> str:
        """构建转码命令"""
        ffmpeg_cmd = self._get_ffmpeg_command()
        
        # 初始化参数列表
        ffmpeg_params = []
        
        # 优先使用转码模板
        if tempid and db:
            # 根据模板ID查询模板
            template = db.query(TranscodeTemplate).filter(
                TranscodeTemplate.id == tempid               
            ).first()            
            if not template:
                raise ValueError(f"转码模板ID {tempid} 不存在或已禁用")
            
            # 根据模板构建FFmpeg参数
            ffmpeg_params.extend([
                f"-c:v {template.video_codec}",
                f"-b:v {template.video_bitrate}k",
                f"-s {template.video_resolution}",
                f"-r {template.video_fps}",
                f"-c:a {template.audio_codec}",
                f"-b:a {template.audio_bitrate}k",
                f"-ar {template.audio_sample_rate}",
                f"-ac {template.audio_channels}"
            ])
            
            # # 添加视频预设和档次
            # if template.video_preset:
            #     ffmpeg_params.append(f"-preset {template.video_preset}")
            # if template.video_profile:
            #     ffmpeg_params.append(f"-profile:v {template.video_profile}")
            
            # 添加水印参数
            if template.image_watermark_enabled and template.image_watermark_path:
                watermark_params = f"-i {template.image_watermark_path} -filter_complex "
                # 处理水印位置和透明度
                position_map = {
                    "top-left": "overlay=10:10",
                    "top-right": "overlay=W-w-10:10",
                    "bottom-left": "overlay=10:H-h-10",
                    "bottom-right": "overlay=W-w-10:H-h-10",
                    "center": "overlay=(W-w)/2:(H-h)/2"
                }
                position = position_map.get(template.image_watermark_position, "overlay=W-w-10:10")
                
                # 添加透明度
                if template.image_watermark_opacity < 100:
                    opacity = template.image_watermark_opacity / 100
                    position += f",format=rgba,colorchannelmixer=aa={opacity}"
                
                # 添加缩放
                if template.image_watermark_scale != 1.0:
                    position = f"[1:v]scale=iw*{template.image_watermark_scale}:ih*{template.image_watermark_scale}[watermark];[0:v][watermark]{position}"
                else:
                    position = f"[0:v][1:v]{position}"
                
                watermark_params += f"{position}"
                ffmpeg_params.append(watermark_params)
            
            # 添加文字水印
            if template.text_watermark_enabled and template.text_watermark_content:
                text_filter = f"drawtext=text='{template.text_watermark_content}':"
                text_filter += f"fontfile={template.text_watermark_font}:'"
                text_filter += f"fontsize={template.text_watermark_size}:'"
                text_filter += f"fontcolor={template.text_watermark_color.replace('#', '0x')}:'"
                
                # 处理文字位置
                text_position_map = {
                    "top-left": "x=10:y=10",
                    "top-right": "x=W-tw-10:y=10",
                    "bottom-left": "x=10:y=H-th-10",
                    "bottom-right": "x=W-tw-10:y=H-th-10",
                    "center": "x=(W-tw)/2:y=(H-th)/2"
                }
                text_position = text_position_map.get(template.text_watermark_position, "x=W-tw-10:y=H-th-10")
                text_filter += text_position
                
                # 添加文字透明度
                if template.text_watermark_opacity < 100:
                    opacity = template.text_watermark_opacity / 100
                    text_filter += f":alpha={opacity}"
                
                ffmpeg_params.append(text_filter)
            
            # 添加自定义参数
            if template.custom_ffmpeg_args:
                ffmpeg_params.append(template.custom_ffmpeg_args)
        else:
            # 如果没有模板，使用默认设置
            ffmpeg_params.extend([
                "-c:v libx264",
                "-c:a aac",
                "-preset medium"
            ])
        
        # 构建完整的转码命令
        params_str = " ".join(ffmpeg_params)
        command = f'{ffmpeg_cmd} -i "{input_url}" -timeout 30000 {params_str} "{output_url}"'
        
        return command.strip()
       
    async def get_pending_tasks(self, limit: int = 1) -> list:
        """获取待处理任务
        此方法使用数据库事务和行级锁来确保任务不会被多个进程重复获取
        
        Args:
            limit: 限制获取的任务数量，默认为1
        
        Returns:
            待处理任务列表
        """
        try:
            from app.core.database import SessionLocal
            from app.models.task import Task, TaskStatus            
            
            with SessionLocal() as db:
                # 开始事务
                db.begin()
                
                try:
                    # 查询状态为PENDING的任务，并使用行级锁锁定
                    # 注意：当前使用基础的FOR UPDATE语法以兼容MySQL 5.7
                    # MySQL 5.7不支持SKIP LOCKED功能，此为兼容性修改
                    pending_tasks = db.query(Task).filter(
                        Task.status == TaskStatus.PENDING
                    ).limit(limit).with_for_update().all()
                    
                    # 如果有找到任务，立即更新状态为RUNNING，避免其他进程获取
                    for task in pending_tasks:
                        task.status = TaskStatus.RUNNING
                        task.started_at = datetime.utcnow()
                        db.add(task)
                    
                    # 提交事务
                    db.commit()
                    
                    # 返回任务列表，转换为字典格式
                    return [task.to_dict() for task in pending_tasks]
                except Exception as e:
                    # 发生异常时回滚事务
                    db.rollback()
                    raise e
        except Exception as e:
            logger.error(f"获取待处理任务失败: {str(e)}")
            raise Exception(f"获取待处理任务失败: {str(e)}")

    async def create_task_with_queue(self, task_data: Dict[str, Any]) -> Dict[str, Any]:
        """创建任务并加入队列"""
        try:
            from app.core.database import SessionLocal
            from app.models.task import Task, TaskStatus
            from app.models.stream import Stream
            import json
            
            with SessionLocal() as db:
                # 获取流信息
                stream = db.query(Stream).filter(Stream.id == task_data["stream_id"]).first()
                if not stream:
                    raise ValueError("关联流不存在")
                
                # 获取任务类型并拆分为数组（统一处理单个任务和组合任务）
                task_type = task_data["task_type"]
                task_types = task_type.split('_')  # 即使是单个任务也拆分为数组
                created_tasks = []
                
                # 遍历任务类型数组，为每个任务类型创建对应的任务
                for i, current_type in enumerate(task_types):
                    # 生成任务名称
                    if len(task_types) > 1:
                        # 组合任务：生成唯一的子任务名称
                        task_name = f"{task_data['name']}_{current_type}_{i}"
                    else:
                        # 单个任务：使用原始名称
                        task_name = task_data['name']
                        
                    # 创建任务数据副本
                    current_task_data = task_data.copy()
                    current_task_data['name'] = task_name
                    current_task_data['task_type'] = current_type
                    
                    # 构建任务命令
                    task_command = None
                    if current_type == 'record':
                        task_command = self.build_recording_command(
                            stream_url=stream.url,
                            output_dir=self.recording_path,
                            task_id=task_name
                        )
                    elif current_type == 'push':
                        if not current_task_data.get('push_url'):
                            raise ValueError(f"任务{task_name}的转推地址不能为空")
                        task_command = self.build_relay_command(
                            input_url=stream.url,
                            output_url=current_task_data['push_url']
                        )
                    elif current_type == 'transcode':
                        if not current_task_data.get('transcode_url'):
                            raise ValueError(f"任务{task_name}的转码输出路径不能为空")
                        output_format = current_task_data.get('format', 'mp4')
                        quality = current_task_data.get('quality', 'high')
                        template_id = current_task_data.get('template_id')
                        # 从transcode_url中提取第一个RTMP地址
                        transcode_url = current_task_data['transcode_url']
                        if ';' in transcode_url:
                            output_url = transcode_url.split(';')[0].strip()
                        else:
                            output_url = transcode_url
                        
                        # 获取模板ID
                        tempid = current_task_data.get('tempid')
                        if not tempid:
                            raise ValueError(f"任务{task_name}的转码模板ID不能为空")
                        
                        task_command = self.build_transcode_command_live(
                            input_url=stream.url,
                            output_url=output_url,
                            tempid=int(tempid),  # 确保tempid是整数
                            db=db
                        )
                    
                    # 根据不同任务类型创建对应的任务实例
                    if current_type == 'record':
                        # 录制任务：基本参数 + record_path + 存储节点信息
                        # 获取当前服务器配置
                        import json
                        from app.core.config import settings
                        
                        # 构建存储节点信息
                        storage_info = {
                            "server_ip": settings.HOST,
                            "server_port": settings.SERVER_PORT,
                            "recording_path": self.recording_path
                        }
                        
                        task = Task(
                            name=task_name,
                            task_type=current_type,
                            stream_id=current_task_data['stream_id'],
                            record_path=current_task_data.get('record_path'),
                            status=TaskStatus.PENDING,
                            config=task_command,
                            info=json.dumps(storage_info)  # 将存储节点信息保存到info字段
                        )
                    elif current_type == 'push':
                        # 推流任务：基本参数 + push_url
                        task = Task(
                            name=task_name,
                            task_type=current_type,
                            stream_id=current_task_data['stream_id'],
                            push_url=current_task_data.get('push_url'),
                            status=TaskStatus.PENDING,
                            config=task_command
                        )
                    elif current_type == 'transcode':
                        # 转码任务：基本参数 + tempid + transcode_url
                        task = Task(
                            name=task_name,
                            task_type=current_type,
                            stream_id=current_task_data['stream_id'],
                            tempid=current_task_data.get('tempid'),
                            transcode_url=current_task_data.get('transcode_url'),
                            status=TaskStatus.PENDING,
                            config=task_command
                        )
                    else:
                        # 默认情况：只包含所有任务都需要的基本参数
                        task = Task(
                            name=task_name,
                            task_type=current_type,
                            stream_id=current_task_data['stream_id'],
                            status=TaskStatus.PENDING,
                            config=task_command
                        )
                    
                    db.add(task)
                    created_tasks.append(task)
                
                # 提交事务
                db.commit()
                
                # 为每个任务刷新并记录ID
                task_ids = []
                for task in created_tasks:
                    db.refresh(task)
                    task_ids.append(task.id)
                
                # 根据任务数量返回不同格式的结果
                if len(task_ids) == 1:
                    return {
                        "task_id": task_ids[0],
                        "status": "pending",
                        "message": "任务已创建并加入队列"
                    }
                else:
                    return {
                        "task_ids": task_ids,
                        "status": "pending",
                        "message": f"已创建{len(task_ids)}个子任务并加入队列"
                    }
                
        except Exception as e:
            raise Exception(f"创建任务失败: {str(e)}")


    async def update_task_status(self, task_id: str, status: str, error_message: str = None):       
        try:
            from app.core.database import SessionLocal
            from app.models.task import Task, TaskStatus
            
            with SessionLocal() as db:
                # 查找任务
                task = db.query(Task).filter(Task.id == task_id).first()
                
                if not task:
                    raise ValueError(f"任务ID {task_id} 不存在")
                
                # 更新任务状态
                task.status = status
                
                # 如果有错误信息，更新错误信息
                if error_message:
                    task.error_message = error_message
                
                # 如果任务完成或失败，更新结束时间
                if status in [TaskStatus.COMPLETED, TaskStatus.FAILED]:
                    task.ended_at = datetime.utcnow()
                
                # 提交更新
                db.add(task)
                db.commit()
                
                return True
        except Exception as e:
            logger.error(f"更新任务状态失败: {str(e)}")
            raise Exception(f"更新任务状态失败: {str(e)}")

    async def update_task_info(self, task_id: str, info: Dict[str, Any] = None, pid: int = None) -> bool:
        """
        更新任务的扩展信息，如开始时间、资源使用情况等，同时支持单独更新pid字段
        
        Args:
            task_id: 任务ID
            info: 包含要更新的信息的字典，可选
            pid: 进程ID，可选
            
        Returns:
            是否更新成功
        """
        try:
            from app.core.database import SessionLocal
            from app.models.task import Task
            import json
            
            with SessionLocal() as db:
                # 查找任务
                task = db.query(Task).filter(Task.id == task_id).first()
                
                if not task:
                    raise ValueError(f"任务ID {task_id} 不存在")
                
                # 如果提供了pid参数，更新数据库中的pid字段
                if pid is not None:
                    task.pid = pid
                
                # 如果提供了info参数，更新info字段
                if info is not None:
                    # 如果任务没有info字段或者info字段为空，初始化为空字典
                    if not task.info:
                        task.info = {}
                    elif isinstance(task.info, str):
                        # 如果info是字符串，尝试解析为JSON
                        try:
                            task.info = json.loads(task.info)
                        except json.JSONDecodeError:
                            # 如果解析失败，初始化为空字典
                            task.info = {}
                    
                    # 更新info字典
                    task.info.update(info)
                    
                    # 如果info是字典，将其转换为字符串存储
                    if isinstance(task.info, dict):
                        task.info = json.dumps(task.info)
                
                # 提交更新
                db.add(task)
                db.commit()
                
                return True
        except Exception as e:
            logger.error(f"更新任务信息失败: {str(e)}")
            raise Exception(f"更新任务信息失败: {str(e)}")

    async def get_task_status(self, task_id: str) -> str:
        """
        获取任务的当前状态
        
        Args:
            task_id: 任务ID
            
        Returns:
            任务状态字符串
        """
        try:
            from app.core.database import SessionLocal
            from app.models.task import Task
            
            with SessionLocal() as db:
                # 查找任务
                task = db.query(Task).filter(Task.id == task_id).first()
                
                if not task:
                    raise ValueError(f"任务ID {task_id} 不存在")
                
                return task.status
        except Exception as e:
            logger.error(f"获取任务状态失败: {str(e)}")
            raise Exception(f"获取任务状态失败: {str(e)}")

# TaskService单例实例
_task_service_instance = None

def get_task_service():
    """获取TaskService单例实例"""
    global _task_service_instance
    if _task_service_instance is None:
        _task_service_instance = TaskService()
    return _task_service_instance